﻿using System;

public class PerspectiveCamera:Camera
{
    public Vector3 position;
    public Vector3 target;
    public Vector3 up;
    public float nearPlane;
    public float farPlane;
    public float fieldOfView;
    public float aspectRatio;
    public float u0, v0;
    public Vector3 u, v, w;
	public PerspectiveCamera(Image img)
	{
        this.img = img;
        this.position = new Vector3(0,0,0);
        this.target = new Vector3(0,0,-1);
        this.up = new Vector3(0,1,0);
        this.nearPlane = 1;
        this.farPlane = 1000;
        this.aspectRatio = (img.width*img.pixelWidth)/(img.height*img.pixelHeight);
        this.fieldOfView = 45;
        this.u0 = -img.width * img.pixelWidth / 2;
        this.v0 = img.height * img.pixelHeight / 2;
        this.w = this.target / (this.target.length());
        this.u = (this.up.cross(w)) / (this.up.cross(w)).length();
        this.v = w.cross(u);
	}
    public PerspectiveCamera(Image img, Vector3 position, Vector3 target)
    {
        this.img = img;
        this.position = position;
        this.target = target;
        this.up = new Vector3(0, 1, 0);
        this.nearPlane = 1;
        this.farPlane = 1000;
        this.aspectRatio = (img.width * img.pixelWidth) / (img.height * img.pixelHeight);
        this.fieldOfView = 45;
        this.u0 = -img.width * img.pixelWidth / 2;
        this.v0 = img.height * img.pixelHeight / 2;
        this.w = -this.target / (this.target.length());
        this.u = (this.up.cross(w)) / ((this.up.cross(w)).length());
        this.v = w.cross(u) / (w.cross(u).length());
    }
    public override void render(Scene scene)
    {
        Console.WriteLine(DateTime.Now.TimeOfDay.ToString() + " Rendering Perspective...");
        int intersectedPrimitive = -1;
        LightIntensity finalColor;
        Ray ray;
        //Ray ray1, ray2, ray3, ray4;//do antyaliasingu
        Vector3 s;
        //Vector3 s1, s2, s3, s4;//do antyaliasingu
        for (int i = 0; i < img.width; i++)
        {
            for (int j = 0; j < img.height; j++)
            {
                s = u * (u0 + (i + 0.5f) * img.pixelWidth) + v * (v0 - (j + 0.5f) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView*Math.PI/180.0f));
                //s1 = u * (u0 + i * img.pixelWidth) + v * (v0 - j * img.pixelHeight) + w * (aspectRatio/2*(float)Math.Tan(fieldOfView));//do antyaliasingu
                //s2 = u * (u0 + (i + 1.0f) * img.pixelWidth) + v * (v0 - j * img.pixelHeight) + w * (aspectRatio/2*(float)Math.Tan(fieldOfView));//do antyaliasingu
                //s3 = u * (u0 + (i + 1.0f) * img.pixelWidth) + v * (v0 - (j + 1.0f) * img.pixelHeight) + w * (aspectRatio/2*(float)Math.Tan(fieldOfView));//do antyaliasingu
                //s4 = u * (u0 + i * img.pixelWidth) + v * (v0 - (j + 1.0f) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView));//do antyaliasingu
                ray = new Ray(this.position, s, false);
                //ray1 = new Ray(this.position, s1, false);//do antyaliasingu
                //ray2 = new Ray(this.position, s2, false);//do antyaliasingu
                //ray3 = new Ray(this.position, s3, false);//do antyaliasingu
                //ray4 = new Ray(this.position, s4, false);//do antyaliasingu
                //img.setPixel(i, j, antialiasing(1, 4, 0.5f, s1, primitive, ray, ray1, ray2, ray3, ray4));//do antyaliasingu
                intersectedPrimitive = -1;
                for (int k = 0; k < scene.objectsNumber(); k++)
                {
                    if (scene.objects[k].intersect(ray))
                        intersectedPrimitive = k;
                }
                if (intersectedPrimitive >= 0)
                {
                    finalColor = Light.light(scene, ray, this.position, intersectedPrimitive, 3);
                    img.setPixel(i, j, finalColor);
                }
                else
                {
                    img.setPixel(i, j, new LightIntensity(0, 0, 0));//bez antyaliasingu
                }
            }
        }
        img.saveImage("obraz2.bmp");
        Console.WriteLine(DateTime.Now.TimeOfDay.ToString() + " Rendering finished");
    }

    public void renderWithPhotonMapping(Scene scene, PhotonMap map)
    {
        Console.WriteLine(DateTime.Now.TimeOfDay.ToString() + " Rendering Perspective...");
        for (int l = 0; l < scene.lightsNumber(); l++)
        {
            map.shootPhotons(scene.lights[l].position, scene.lights[l].color, l, 1000000, scene);
        }
        map.tree.createTree(map.photons, 0, 1);
        int intersectedPrimitive = -1;
        LightIntensity finalColor = new LightIntensity();
        Ray ray;
        Vector3 s;
        for (int i = 0; i < img.width; i++)
        {
            for (int j = 0; j < img.height; j++)
            {
                s = u * (u0 + (i + 0.5f) * img.pixelWidth) + v * (v0 - (j + 0.5f) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView * Math.PI / 180.0f));
                ray = new Ray(this.position, s, false);
                intersectedPrimitive = -1;
                for (int k = 0; k < scene.objectsNumber(); k++)
                {
                    if (scene.objects[k].intersect(ray))
                        intersectedPrimitive = k;
                }
                if (intersectedPrimitive >= 0)
                {
                    if (scene.objects[intersectedPrimitive].material.type == (int)Material.Types.diffuse)
                    {
                        finalColor = map.calculateColor(ray.destination, 5.0f, 4);
                    }
                    else
                    {
                        finalColor = new LightIntensity();
                    }
                    img.setPixel(i, j, finalColor);
                }
                else
                {
                    img.setPixel(i, j, new LightIntensity(0, 0, 0));
                }
            }
        }
        Console.WriteLine(map.count);
        Console.WriteLine(map.count2);
        img.saveImage("render4.bmp");
        //img.saveImage("obraz2.bmp");
        Console.WriteLine(DateTime.Now.TimeOfDay.ToString() + " Rendering finished");
    }

    public void renderFinal(Scene scene, PhotonMap map, PhotonMap causticMap)
    {
        Console.WriteLine(DateTime.Now.TimeOfDay.ToString() + " Rendering Perspective...");
        for (int l = 0; l < scene.lightsNumber(); l++)
        {
            for (int k = 0; k < scene.objectsNumber(); k++)
            {
                if (scene.objects[k].material.type == (int)Material.Types.mirror || scene.objects[k].material.type == (int)Material.Types.refraction)
                    causticMap.shootCausticPhotons(scene.lights[l].position, scene.objects[k].getCentre(), scene.objects[k].getWidth(), scene.lights[l].color, l, 100000, scene);
            }
        }
        causticMap.tree.createTree(causticMap.photons, 0, 1);
        for (int l = 0; l < scene.lightsNumber(); l++)
        {
            map.shootPhotons(scene.lights[l].position, scene.lights[l].color, l, 1000000, scene);
        }
        map.tree.createTree(map.photons, 0, 1);
        int intersectedPrimitive = -1;
        LightIntensity finalColor = new LightIntensity();
        Ray ray;
        Vector3 s;
        for (int i = 0; i < img.width; i++)
        {
            for (int j = 0; j < img.height; j++)
            {
                s = u * (u0 + (i + 0.5f) * img.pixelWidth) + v * (v0 - (j + 0.5f) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView * Math.PI / 180.0f));
                ray = new Ray(this.position, s, false);
                intersectedPrimitive = -1;
                for (int k = 0; k < scene.objectsNumber(); k++)
                {
                    if (scene.objects[k].intersect(ray))
                        intersectedPrimitive = k;
                }
                if (intersectedPrimitive >= 0)
                {
                    if (scene.objects[intersectedPrimitive].material.type == (int)Material.Types.mirror || scene.objects[intersectedPrimitive].material.type == (int)Material.Types.refraction)
                    {
                        finalColor = Light.light(scene, ray, this.position, intersectedPrimitive, 3);
                    }
                    else
                    {
                        finalColor = map.calculateColor(ray.destination, 8.0f, 4)
                                     +causticMap.calculateColor(ray.destination, 4.0f, 2)
                                     + Light.light(scene, ray, this.position, intersectedPrimitive, 3);
                    }
                    img.setPixel(i, j, finalColor);
                }
                else
                {
                    img.setPixel(i, j, new LightIntensity(0, 0, 0));
                }
            }
        }
        Console.WriteLine(map.count);
        Console.WriteLine(map.count2);
        img.saveImage("renderFinal5.bmp");
        //img.saveImage("obraz2.bmp");
        Console.WriteLine(DateTime.Now.TimeOfDay.ToString() + " Rendering finished");
    }

    public LightIntensity antialiasing(int minSamples, int maxSamples, float k, Vector3 pos, Primitive[] p, Ray r0, Ray r1, Ray r2, Ray r3, Ray r4)
    {
        LightIntensity li0, li1, li2, li3, li4;
        li0 = new LightIntensity(0, 0, 0);
        li1 = new LightIntensity(0, 0, 0);
        li2 = new LightIntensity(0, 0, 0);
        li3 = new LightIntensity(0, 0, 0);
        li4 = new LightIntensity(0, 0, 0);
        for (int i = 0; i < p.Length; i++)
        {
            if (p[i].intersect(r0))
                li0 = p[i].material.Kd;
            if (p[i].intersect(r1))
                li1 = p[i].material.Kd;
            if (p[i].intersect(r2))
                li2 = p[i].material.Kd;
            if (p[i].intersect(r3))
                li3 = p[i].material.Kd;
            if (p[i].intersect(r4))
                li4 = p[i].material.Kd;
        }
        if (minSamples < maxSamples)
        {
            //LightIntensity liAvg = new LightIntensity((li1.r+li2.r+li3.r+li4.r)/4,(li1.g+li2.g+li3.g+li4.g)/4,(li1.b+li2.b+li3.b+li4.b)/4);
            /*if (Math.Abs(li0.r - liAvg.r) > difR || Math.Abs(li0.g - liAvg.g) > difG || Math.Abs(li0.b - liAvg.b) > difB)
            {
                return (antialiasing(minSamples++, maxSamples, pixelWidth / 2, pixelHeight / 2, v, p, new Ray(new Vector3(r0.origin.x - pixelWidth / 4, r0.origin.y + pixelHeight / 4, r0.origin.z), v, true), r1, new Ray(new Vector3(r0.origin.x, r0.origin.y + pixelHeight / 2, r0.origin.z), v, true), r0, new Ray(new Vector3(r0.origin.x - pixelWidth / 2, r0.origin.y, r0.origin.z), v, true))
                        + antialiasing(minSamples++, maxSamples, pixelWidth / 2, pixelHeight / 2, v, p, new Ray(new Vector3(r0.origin.x + pixelWidth / 4, r0.origin.y + pixelHeight / 4, r0.origin.z), v, true), new Ray(new Vector3(r0.origin.x, r0.origin.y + pixelHeight / 2, r0.origin.z), v, true), r2, new Ray(new Vector3(r0.origin.x + pixelWidth / 2, r0.origin.y, r0.origin.z), v, true),r0)
                        + antialiasing(minSamples++, maxSamples, pixelWidth / 2, pixelHeight / 2, v, p, new Ray(new Vector3(r0.origin.x + pixelWidth / 4, r0.origin.y - pixelHeight / 4, r0.origin.z), v, true), r0, new Ray(new Vector3(r0.origin.x + pixelWidth / 2, r0.origin.y, r0.origin.z), v, true), r3, new Ray(new Vector3(r0.origin.x, r0.origin.y - pixelHeight/2, r0.origin.z), v, true))
                        + antialiasing(minSamples++, maxSamples, pixelWidth / 2, pixelHeight / 2, v, p, new Ray(new Vector3(r0.origin.x - pixelWidth / 4, r0.origin.y - pixelHeight / 4, r0.origin.z), v, true), new Ray(new Vector3(r0.origin.x - pixelWidth / 2, r0.origin.y, r0.origin.z), v, true), r0, new Ray(new Vector3(r0.origin.x, r0.origin.y - pixelHeight / 2, r0.origin.z), v, true), r4)) / 4;
            }
            else 
            {
                return li0;
            }*/
            if (Math.Abs(li0.r - li1.r) > difR || Math.Abs(li0.g - li1.g) > difG || Math.Abs(li0.b - li1.b) > difB)
            {
                //li1 = (antialiasing(++minSamples, maxSamples, pixelXPos, pixelYPos, k / 2, p, new Ray(r0.origin, new Vector3(u * (u0 + (pixelXPos + k / 2) * img.pixelWidth) + v * (v0 - (pixelYPos + k / 2) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r1, new Ray(r0.origin, new Vector3(u * (u0 + (pixelXPos + k) * img.pixelWidth) + v * (v0 - pixelYPos * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r0, new Ray(r0.origin, new Vector3(u * (u0 + pixelXPos * img.pixelWidth) + v * (v0 - (pixelYPos + k) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false)));
                li1 = (antialiasing(++minSamples, maxSamples, k / 2, pos, p, new Ray(r0.origin, new Vector3(u * (pos.x + (k / 2) * img.pixelWidth) + v * (pos.y - (k / 2) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r1, new Ray(r0.origin, new Vector3(u * (pos.x + (k) * img.pixelWidth) + v * (pos.y - 0 * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r0, new Ray(r0.origin, new Vector3(u * (pos.x + 0 * img.pixelWidth) + v * (pos.y - (k) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false)));
            }
            if (Math.Abs(li0.r - li2.r) > difR || Math.Abs(li0.g - li2.g) > difG || Math.Abs(li0.b - li2.b) > difB)
            {
                //li2 = (antialiasing(++minSamples, maxSamples, pixelXPos, pixelYPos, k / 2, p, new Ray(r0.origin, new Vector3(u * (u0 + (pixelXPos + 3 * k / 2) * img.pixelWidth) + v * (v0 - (pixelYPos + k / 2) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), new Ray(r0.origin, new Vector3(u * (u0 + (pixelXPos + k) * img.pixelWidth) + v * (v0 - pixelYPos * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r2, new Ray(r0.origin, new Vector3(u * (u0 + (pixelXPos + 2*k) * img.pixelWidth) + v * (v0 - (pixelYPos + k) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r0));
                li2 = (antialiasing(++minSamples, maxSamples, k / 2, new Vector3(pos.x + k * img.pixelWidth, pos.y, pos.z), p, new Ray(r0.origin, new Vector3(u * (pos.x + (3 * k / 2) * img.pixelWidth) + v * (pos.y - (k / 2) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), new Ray(r0.origin, new Vector3(u * (pos.x + (k) * img.pixelWidth) + v * (pos.y - 0 * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r2, new Ray(r0.origin, new Vector3(u * (pos.x + (2 * k) * img.pixelWidth) + v * (pos.y - (k) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r0));
            }
            if (Math.Abs(li0.r - li3.r) > difR || Math.Abs(li0.g - li3.g) > difG || Math.Abs(li0.b - li3.b) > difB)
            {
                //li3 = (antialiasing(++minSamples, maxSamples, pixelXPos, pixelYPos, k / 2, p, new Ray(r0.origin, new Vector3(u * (u0 + (pixelXPos + 3 * k / 2) * img.pixelWidth) + v * (v0 - (pixelYPos + 3 * k / 2) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r0, new Ray(r0.origin, new Vector3(u * (u0 + (pixelXPos + 2 * k) * img.pixelWidth) + v * (v0 - (pixelYPos + k) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r3, new Ray(r0.origin, new Vector3(u * (u0 + (pixelXPos + k) * img.pixelWidth) + v * (v0 - (pixelYPos + 2 * k) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false)));
                li3 = (antialiasing(++minSamples, maxSamples, k / 2, new Vector3(pos.x + k * img.pixelWidth, pos.y - k * img.pixelHeight, pos.z), p, new Ray(r0.origin, new Vector3(u * (pos.x + (3 * k / 2) * img.pixelWidth) + v * (pos.y - (3 * k / 2) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r0, new Ray(r0.origin, new Vector3(u * (pos.x + (2 * k) * img.pixelWidth) + v * (pos.y - (k) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r3, new Ray(r0.origin, new Vector3(u * (pos.x + (k) * img.pixelWidth) + v * (pos.y - (2 * k) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false)));
            }
            if (Math.Abs(li0.r - li4.r) > difR || Math.Abs(li0.g - li4.g) > difG || Math.Abs(li0.b - li4.b) > difB)
            {
                //li4 = (antialiasing(++minSamples, maxSamples, pixelXPos, pixelYPos, k / 2, p, new Ray(r0.origin, new Vector3(u * (u0 + (pixelXPos + k / 2) * img.pixelWidth) + v * (v0 - (pixelYPos + 3 * k / 2) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), new Ray(r0.origin, new Vector3(u * (u0 + pixelXPos * img.pixelWidth) + v * (v0 - (pixelYPos + k) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r0, new Ray(r0.origin, new Vector3(u * (u0 + (pixelXPos + k) * img.pixelWidth) + v * (v0 - (pixelYPos + 2 * k) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r4));
                li4 = (antialiasing(++minSamples, maxSamples, k / 2, new Vector3(pos.x, pos.y - k * img.pixelHeight, pos.z), p, new Ray(r0.origin, new Vector3(u * (pos.x + (k / 2) * img.pixelWidth) + v * (pos.y - (3 * k / 2) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), new Ray(r0.origin, new Vector3(u * (pos.x + 0 * img.pixelWidth) + v * (pos.y - (k) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r0, new Ray(r0.origin, new Vector3(u * (pos.x + (k) * img.pixelWidth) + v * (pos.y - (2 * k) * img.pixelHeight) + w * (aspectRatio / 2 * (float)Math.Tan(fieldOfView))), false), r4));
            }
            //return (li1 + li2 + li3 + li4) / 4;
            return ((li1+li0)/2 + (li2+li0)/2 + (li3+li0)/2 + (li4+li0)/2) / 4;
        }
        else
        {
            return new LightIntensity(((li1.r + li2.r + li3.r + li4.r) / 2 + 2 * li0.r) / 4, ((li1.g + li2.g + li3.g + li4.g) / 2 + 2 * li0.g) / 4, ((li1.b + li2.b + li3.b + li4.b) / 2 + 2 * li0.b) / 4);
        }
    }
}
